/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "AppService.h"
#include <regex>
#include "Log.h"
#include "ErrorCode.h"
#include "PluginGeneralTypes.h"
#include "JsonTransUtil.h"
#include "CommonStruct.h"
#include "manager/CloudServiceManager.h"
#include "interface/CloudServiceRequest.h"
#include "interface/CloudServiceResponse.h"
#include "kmcv3.h"
#include "securec.h"
#include "utils/PluginUtilities.h"
#include "utils/CertManager.h"

using namespace Module;
using namespace AppProtect;

namespace {
constexpr auto MODULE = "AppService";
constexpr int NUM_64 = 64;
const std::string USE_HTTPS = "1";
const std::string USE_PROXY = "1";
const int DEFAULT_PAGE_NO = 1;
const int DEFAULT_PAGE_SIZE = 200;
constexpr uint32_t NUMBER1 = 1;
}  // namespace

namespace AppServiceExport {

int Encrypt(const std::string &plainText, std::string &cipherText)
{
    HCP_Log(DEBUG, MODULE) << "Enter Encrypt." << HCPENDLOG;
    char *enPassWord = nullptr;
    if (EncryptV3c(KMC_DOMAIN::KMC_SHARE_INNER_DOMAIN, plainText.c_str(), &enPassWord) != KMC_RET::KMC_SUCESS) {
        HCP_Log(ERR, MODULE) << "Decrypt failed." << HCPENDLOG;
        return MP_FAILED;
    }
    if (enPassWord != nullptr) {
        cipherText = enPassWord;
        uint64_t len = cipherText.length();
        memset_s(enPassWord, len, 0, len);
    }
    if (cipherText.empty()) {
        HCP_Log(ERR, MODULE) << "CipherText is empty string." << HCPENDLOG;
        KmcFree(enPassWord);
        return MP_FAILED;
    }
    KmcFree(enPassWord);
    return MP_SUCCESS;
}

int Decrypt(std::string &plainText, const std::string &cipherText)
{
    HCP_Log(DEBUG, MODULE) << "Enter Decrypt." << HCPENDLOG;
    char *keyData = nullptr;
    if (DecryptV3c(KMC_DOMAIN::KMC_SHARE_INNER_DOMAIN, &keyData, cipherText.c_str()) != KMC_RET::KMC_SUCESS) {
        HCP_Log(ERR, MODULE) << "Decrypt failed." << HCPENDLOG;
        return Module::FAILED;
    }
    if (keyData != nullptr) {
        plainText = keyData;
        uint64_t len = plainText.length();
        memset_s(keyData, len, 0, len);
    }
    if (plainText.empty()) {
        HCP_Log(ERR, MODULE) << "PlainText is empty string." << HCPENDLOG;
        KmcFree(keyData);
        return Module::FAILED;
    }
    KmcFree(keyData);
    keyData = nullptr;
    return Module::SUCCESS;
}

void TransformCloudServiceMsg(StorageVerifyInfo &cloudServiceMsg, const AuthExtendInfo &extendInfo)
{
    cloudServiceMsg.accessKey = extendInfo.ak;
    cloudServiceMsg.secretKey = extendInfo.sk;
    cloudServiceMsg.endPoint = extendInfo.endPoint;
    if (USE_HTTPS == extendInfo.useHttps) {
        cloudServiceMsg.useHttps = true;
        if (!extendInfo.certification.empty() && extendInfo.certification.length() != 0) {
            cloudServiceMsg.certHttps = OBSPlugin::Base64Decode(extendInfo.certification);
        }
    }
    if (USE_PROXY == extendInfo.proxyEnable) {
        cloudServiceMsg.useProxy = true;
        cloudServiceMsg.proxyHostName = extendInfo.proxyHostName;
        cloudServiceMsg.proxyUserName = extendInfo.proxyUserName;
        cloudServiceMsg.proxyUserPwd = extendInfo.proxyUserPwd;
    }
}

void CheckApplication(ActionResult &returnValue, const ApplicationEnvironment &appEnv, const Application &application)
{
    HCP_Log(INFO, MODULE) << "Enter of Obs Plugin CheckApplication " << HCPENDLOG;
    AuthExtendInfo extendInfo{};
    ApplicationExtendInfo appExtendInfo{};
    if (!InitValue(application, returnValue, extendInfo, appExtendInfo)) {
        return;
    }

    StorageVerifyInfo serviceMsg;
    TransformCloudServiceMsg(serviceMsg, extendInfo);
    if (!AddIpRule(serviceMsg)) {
        HCP_Log(ERR, MODULE) << "AddIpRule failed." << HCPENDLOG;
        returnValue.__set_code(Module::ERROR_OBS_RESOURCE_ENDPOINT_CONNECT_FAILED);
        returnValue.__set_bodyErr(Module::ERROR_OBS_RESOURCE_ENDPOINT_CONNECT_FAILED);
        return;
    }
    OBSPlugin::CertManager certMgr(serviceMsg.accessKey);
    (void)certMgr.SaveFile(serviceMsg.certHttps);
    serviceMsg.caPath = certMgr.GetCAPath();
    serviceMsg.caFile = certMgr.GetCAFile();
    StorageConfig storageConfig;
    storageConfig.storageType = static_cast<StorageType>(std::stoi(appExtendInfo.storageType));
    storageConfig.verifyInfo = serviceMsg;
    auto cloudService = CloudServiceManager::CreateInst(storageConfig);
    if (cloudService == nullptr) {
        HCP_Log(ERR, MODULE) << "CreateInst Failed" << HCPENDLOG;
        certMgr.RemoveCertFile();
        return;
    }
    auto checkConnectRequest = std::make_unique<CheckConnectRequest>();
    OBSResult connectResult = cloudService->CheckConnect(checkConnectRequest);
    if (connectResult.IsSucc()) {
        returnValue.__set_code(MP_SUCCESS);
        returnValue.__set_bodyErr(MP_SUCCESS);
        certMgr.RemoveCertFile();
        HCP_Log(INFO, MODULE) << "CheckApplication Success, ak: " << serviceMsg.accessKey << HCPENDLOG;
        return;
    }
    int64_t commonErrorCode = connectResult.GetCommonErrorCode();
    if (commonErrorCode == Module::OBS_INTERNAL_ERROR_CODE) {
        commonErrorCode = Module::OBS_CONNECTIVITY_ERROR_CODE;
    }
    returnValue.__set_code(commonErrorCode);
    returnValue.__set_bodyErr(commonErrorCode);
    returnValue.__set_message(connectResult.errorDesc);
    certMgr.RemoveCertFile();
    HCP_Log(ERR, MODULE) << "CheckApplication Failed(" << connectResult.errorDesc << "), ak: " << serviceMsg.accessKey
                         << "commonErrorCode: " << commonErrorCode << HCPENDLOG;
}

bool InitValue(const Application &application, ActionResult &returnValue, AuthExtendInfo &extendInfo,
    ApplicationExtendInfo &appExtendInfo)
{
    returnValue.__set_code(Module::OBS_INTERNAL_ERROR_CODE);
    returnValue.__set_bodyErr(Module::OBS_INTERNAL_ERROR_CODE);  // bodyErr 错误码
    if (!Module::JsonHelper::JsonStringToStruct(application.auth.extendInfo, extendInfo) ||
        !Module::JsonHelper::JsonStringToStruct(application.extendInfo, appExtendInfo)) {
        HCP_Log(ERR, MODULE) << "Auth Info is invaild." << HCPENDLOG;
        returnValue.__set_message("Auth Info is invaild.");
        return false;
    }
    return true;
}

void ListApplicationResourceV2(ResourceResultByPage &returnValue, const ListResourceRequest &request)
{
    HCP_Log(INFO, MODULE) << "Enter of Obs Plugin ListApplicationResourceV2 " << HCPENDLOG;

    AuthExtendInfo extendInfo{};
    ApplicationExtendInfo appExtendInfo{};
    if (!Module::JsonHelper::JsonStringToStruct(request.appEnv.auth.extendInfo, extendInfo) ||
        !Module::JsonHelper::JsonStringToStruct(request.appEnv.extendInfo, appExtendInfo)) {
        HCP_Log(ERR, MODULE) << "Auth Info is invaild." << HCPENDLOG;
        return;
    }
    returnValue.pageNo = DEFAULT_PAGE_NO;
    returnValue.pageSize = DEFAULT_PAGE_SIZE;
    returnValue.total = 0;

    StorageVerifyInfo serviceMsg;
    TransformCloudServiceMsg(serviceMsg, extendInfo);
    OBSPlugin::CertManager certMgr(serviceMsg.accessKey);
    (void)certMgr.SaveFile(serviceMsg.certHttps);
    serviceMsg.caPath = certMgr.GetCAPath();
    serviceMsg.caFile = certMgr.GetCAFile();
    StorageConfig storageConfig;
    storageConfig.storageType = static_cast<StorageType>(std::stoi(appExtendInfo.storageType));
    storageConfig.verifyInfo = serviceMsg;
    auto cloudService = CloudServiceManager::CreateInst(storageConfig);
    if (cloudService == nullptr) {
        certMgr.RemoveCertFile();
        HCP_Log(ERR, MODULE) << "CreateInst Failed" << HCPENDLOG;
        return;
    }
    auto listBucketsRequest = std::make_unique<ListBucketsRequest>();
    std::unique_ptr<ListBucketsResponse> listBucketsResponse;
    if (cloudService->ListBuckets(listBucketsRequest, listBucketsResponse).IsSucc()) {
        std::vector<std::string> bucketList = listBucketsResponse->bucketList;
        returnValue.total = bucketList.size();
        for (uint16_t i = 0; i < bucketList.size(); i++) {
            ApplicationResource resource;
            resource.__set_id(std::to_string(i));
            resource.__set_name(bucketList[i]);
            returnValue.items.push_back(resource);
        }
        HCP_Log(INFO, MODULE) << "ListBuckets success." << HCPENDLOG;
    } else {
        HCP_Log(ERR, MODULE) << "ListBuckets failed." << HCPENDLOG;
    }
    certMgr.RemoveCertFile();
}

bool AddIpRule(const StorageVerifyInfo& serviceMsg)
{
    std::string oriTargetIp = serviceMsg.useProxy ? serviceMsg.proxyHostName : serviceMsg.endPoint;
    std::string targetIp;
    std::regex pattern("([^/:]+)(:\\d+)?");
    std::smatch result;
    if (std::regex_match(oriTargetIp, result, pattern)) {
        targetIp = result[NUMBER1];
    } else {
        targetIp = oriTargetIp;
    }
    std::string targetObsIP = OBSPlugin::ParseDomain2IP(targetIp);
    if (targetObsIP.empty()) {
        HCP_Log(ERR, MODULE) << "AddIpRule failed.Parse domain ip failed:" << targetIp << HCPENDLOG;
        return false;
    }
    std::vector<std::string> ipList;
    ipList.push_back(targetObsIP);
    if (!OBSPlugin::OperateIpsRule(ipList, Module::IpRuleOperation::ADD)) {
        HCP_Log(ERR, MODULE) << "AddIpRule failed.targetIp: " << targetObsIP << HCPENDLOG;
        return false;
    }
    HCP_Log(DEBUG, MODULE) << "AddIpRule success.targetIp: " << targetObsIP << HCPENDLOG;
    return true;
}
}  // namespace AppServiceExport
